{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tqrD7Yzlmlsk"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "2k8X1C1nmpKv"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "32xflLc4NTx-"
      },
      "source": [
        "# 사용자 정의 페더레이션 알고리즘, 2부: 페더레이션 평균화 구현하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jtATV6DlqPs0"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/federated/tutorials/custom_federated_algorithms_2\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/federated/tutorials/custom_federated_algorithms_2.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행하기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/federated/tutorials/custom_federated_algorithms_2.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서 소스 보기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_igJ2sfaNWS8"
      },
      "source": [
        "이 튜토리얼은 [페더레이션 학습(FL)](../federated_learning.md) 레이어(`tff.learning`)의 기반 역할을 하는 [Federated Core(FC)](../federated_core.md)를 사용하여 TFF에서 사용자 정의 형태의 페더레이션 알고리즘을 구현하는 방법을 보여주는 2부작 시리즈의 두 번째 부분입니다.\n",
        "\n",
        "여기에 사용된 핵심 개념과 프로그래밍 추상화를 소개하는 [이 시리즈의 첫 번째 부분](custom_federated_algorithms_1.ipynb)을 먼저 읽어보기 바랍니다.\n",
        "\n",
        "시리즈의 두 번째 부분에서는 첫 번째 부분에서 소개한 메커니즘을 사용하여 페더레이션 훈련 및 평가 알고리즘의 간단한 버전을 구현합니다.\n",
        "\n",
        "TFF의 페더레이션 학습 API를 더 개괄적이고 평이한 수준으로 소개하는 [이미지 분류](federated_learning_for_image_classification.ipynb) 및 [텍스트 생성](federated_learning_for_text_generation.ipynb) 튜토리얼을 살펴볼 것을 권장합니다. 여기에서 설명하는 개념에 대한 배경을 이해하는 데 도움이 됩니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cuJuLEh2TfZG"
      },
      "source": [
        "## 시작하기 전에\n",
        "\n",
        "시작하기 전에 다음 \"Hello World\" 예제를 실행하여 환경이 올바르게 설정되었는지 확인하기 바랍니다. 작동하지 않으면 [설치](../install.md) 가이드의 지침을 참조하세요."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "rB1ovcX1mBxQ"
      },
      "outputs": [],
      "source": [
        "#@test {\"skip\": true}\n",
        "!pip install --quiet --upgrade tensorflow_federated_nightly\n",
        "!pip install --quiet --upgrade nest_asyncio\n",
        "\n",
        "import nest_asyncio\n",
        "nest_asyncio.apply()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-skNC6aovM46"
      },
      "outputs": [],
      "source": [
        "import collections\n",
        "\n",
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "import tensorflow_federated as tff\n",
        "\n",
        "# TODO(b/148678573,b/148685415): must use the ReferenceExecutor because it\n",
        "# supports unbounded references and tff.sequence_* intrinsics.\n",
        "tff.framework.set_default_context(tff.test.ReferenceExecutor())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zzXwGnZamIMM"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'Hello, World!'"
            ]
          },
          "execution_count": 4,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "@tff.federated_computation\n",
        "def hello_world():\n",
        "  return 'Hello, World!'\n",
        "\n",
        "hello_world()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iu5Gd8D6W33s"
      },
      "source": [
        "## 페더레이션 평균화 구현하기\n",
        "\n",
        "[이미지 분류를 위한 페더레이션 학습](federated_learning_for_image_classification.ipynb)에서와 같이 MNIST 예제를 사용할 것입니다. 그러나 이 튜토리얼은 낮은 수준에서 준비한 것이므로 Keras API 및 `tff.simulation`을 건너뛰고 원시 모델 코드를 작성하며 페더레이션 데이터세트를 처음부터 구성합니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b6qCjef350c_"
      },
      "source": [
        "### 페더레이션 데이터세트 준비하기\n",
        "\n",
        "데모를 위해 10명의 사용자로부터 얻은 데이터가 있고 각 사용자가 다른 숫자를 인식하는 방식에 대한 지식을 제공하는 시나리오를 시뮬레이션할 것입니다. 이 작업은 [i.i.d.](https://en.wikipedia.org/wiki/Independent_and_identically_distributed_random_variables)로서 이루어집니다.\n",
        "\n",
        "먼저 표준 MNIST 데이터를 로드하겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "uThZM4Ds-KDQ"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz\n",
            "11493376/11490434 [==============================] - 0s 0us/step\n",
            "11501568/11490434 [==============================] - 0s 0us/step\n"
          ]
        }
      ],
      "source": [
        "mnist_train, mnist_test = tf.keras.datasets.mnist.load_data()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "PkJc5rHA2no_"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "[(dtype('uint8'), (60000, 28, 28)), (dtype('uint8'), (60000,))]"
            ]
          },
          "execution_count": 6,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "[(x.dtype, x.shape) for x in mnist_train]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mFET4BKJFbkP"
      },
      "source": [
        "데이터는 Numpy 배열로 제공되며, 하나는 이미지가 있고 다른 하나는 숫자 레이블이 있으며, 둘 모두 첫 번째 차원은 개별 예제로 진행됩니다. 페더레이션 시퀀스를 TFF 계산에 제공하는 방식과 호환되는 방식으로 형식을 지정하는 도우미 함수를 작성해 보겠습니다. 즉, 목록의 목록이 이용되며 바깥 목록은 사용자(숫자)를 나열하고 안쪽 목록은 각 클라이언트 시퀀스에서 데이터 배치를 나열합니다. 항상 하던 대로 각각 선행하는 배치 차원이 있는 `x` 및 `y`라는 텐서 쌍으로 각 배치를 구성합니다. 여기서 또한 데이터 변환으로 모델 논리를 복잡하게 만들 필요가 없도록 각 이미지를 784개 요소 벡터로 평면화하고 그 내부의 픽셀 크기를 `0..1` 범위로 재조정합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "XTaTLiq5GNqy"
      },
      "outputs": [],
      "source": [
        "NUM_EXAMPLES_PER_USER = 1000\n",
        "BATCH_SIZE = 100\n",
        "\n",
        "\n",
        "def get_data_for_digit(source, digit):\n",
        "  output_sequence = []\n",
        "  all_samples = [i for i, d in enumerate(source[1]) if d == digit]\n",
        "  for i in range(0, min(len(all_samples), NUM_EXAMPLES_PER_USER), BATCH_SIZE):\n",
        "    batch_samples = all_samples[i:i + BATCH_SIZE]\n",
        "    output_sequence.append({\n",
        "        'x':\n",
        "            np.array([source[0][i].flatten() / 255.0 for i in batch_samples],\n",
        "                     dtype=np.float32),\n",
        "        'y':\n",
        "            np.array([source[1][i] for i in batch_samples], dtype=np.int32)\n",
        "    })\n",
        "  return output_sequence\n",
        "\n",
        "\n",
        "federated_train_data = [get_data_for_digit(mnist_train, d) for d in range(10)]\n",
        "\n",
        "federated_test_data = [get_data_for_digit(mnist_test, d) for d in range(10)]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xpNdBimWaMHD"
      },
      "source": [
        "빠른 온전성 검사로 다섯 번째 클라이언트(숫자 `5`에 해당)가 제공한 데이터의 마지막 배치에서 `Y` 텐서를 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bTNuL1W4bcuc"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "array([5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n",
              "       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n",
              "       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n",
              "       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n",
              "       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5], dtype=int32)"
            ]
          },
          "execution_count": 8,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "federated_train_data[5][-1]['y']"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Xgvcwv7Obhat"
      },
      "source": [
        "확실히 하기 위해 해당 배치의 마지막 요소에 해당하는 이미지도 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cI4aat1za525"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\n67z11ltx6eefzp8/7xw/ftxxHMe5cuWKk5ub65w6dSru+y5QX/HabzE/8jc1NSknJ0fZ2dnq16+f\n5s2bp/r6+li3cVcoKytTWlraLffV19ersrJSklRZWam6urp4tHbb3hKF2+1WUVGRpFtnlo73vgvU\nV7zEPPxtbW0aPnx47+3MzMyEmvI7KSlJU6ZMUXFxsTweT7zb+Zf29vbemZHcbrc6Ojri3NGtQpm5\nOZb+ObN0Iu27cGa8jrSYh9+5zeBCIs3k09jYqO+//14HDhzQtm3b9PXXX8e7pbvG8uXL1dzcLJ/P\nJ7fbrdWrV8e1n6tXr6qiokJbtmzR4MGD49rLP/1/X/HabzEPf2ZmplpbW3tvnzt3ThkZGbFuI6D/\n9uJyuTRz5kw1NTXFuaNbpaen906S6vf75XK54tzR/6SnpyslJUXJyclatmxZXPddd3e3KioqtHDh\nQs2aNau3v3jvu0B9xWO/xTz8TzzxhM6cOaOzZ8/qxo0b+uSTT1ReXh7rNm7r2rVr6urq6v354MGD\nCTf7cHl5uWprayVJtbW1mj59epw7+p9EmbnZCTCzdLz3XaC+4rbfYn6K0XGc/fv3O7m5uU52draz\nfv36eLRwW83NzU5hYaFTWFjoPP7443Hvbd68ec7DDz/spKamOsOGDXN27NjhXLp0yZk4caKTk5Pj\nTJw40fn1118TprdFixY5+fn5TkFBgfPCCy8458+fj0tv33zzjSPJKSgocMaMGeOMGTPG2b9/f9z3\nXaC+4rXfuMIPsBRX+AGWIvyApQg/YCnCD1iK8AOWIvyApQg/YCnCD1jqP1hNIrYb+rn+AAAAAElF\nTkSuQmCC\n",
            "text/plain": [
              "<Figure size 600x400 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "from matplotlib import pyplot as plt\n",
        "\n",
        "plt.imshow(federated_train_data[5][-1]['x'][-1].reshape(28, 28), cmap='gray')\n",
        "plt.grid(False)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "J-ox58PA56f8"
      },
      "source": [
        "### TensorFlow와 TFF를 결합할 때\n",
        "\n",
        "이 튜토리얼에서는 간결하게 하기 위해 TensorFlow 논리를 도입하는 함수를 `tff.tf_computation`으로 즉시 데코레이팅합니다. 그러나 더 복잡한 논리의 경우 권장되는 패턴은 아닙니다. 그러지 않아도 TensorFlow 디버깅이 어려움을 줄 수 있는데, 완전히 직렬화한 다음 다시 가져온 후 TensorFlow를 디버깅하려면 필연적으로 일부 메타데이터가 손실되고 상호 작용이 제한되어 디버깅이 훨씬 더 어려워집니다.\n",
        "\n",
        "따라서 **복잡한 TF 논리를 독립 실행형 Python 함수로 작성할 것을 강력히 권장합니다**(즉, `tff.tf_computation` 데코레이션을 사용하지 않음). 이렇게 하면 TFF에 대한 계산을 직렬화하기 전에 TF 모범 사례 및 도구(예: 즉시 실행 모드)를 사용하여 TensorFlow 논리를 개발하고 테스트할 수 있습니다(예: Python 함수를 인수로 사용하여 `tff.tf_computation`을 호출)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RSd6UatXbzw-"
      },
      "source": [
        "### 손실 함수 정의하기\n",
        "\n",
        "이제 데이터를 얻었으므로 훈련에 사용할 수 있는 손실 함수를 정의하겠습니다. 먼저 입력 유형을 tuple이라는 TFF로 정의하겠습니다. 데이터 배치의 크기가 다를 수 있으므로 배치 차원을 `None`으로 설정하여 이 차원의 크기를 알 수 없는 것으로 나타냅니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "653xv5NXd4fy"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'<x=float32[?,784],y=int32[?]>'"
            ]
          },
          "execution_count": 10,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "BATCH_SPEC = collections.OrderedDict(\n",
        "    x=tf.TensorSpec(shape=[None, 784], dtype=tf.float32),\n",
        "    y=tf.TensorSpec(shape=[None], dtype=tf.int32))\n",
        "BATCH_TYPE = tff.to_type(BATCH_SPEC)\n",
        "\n",
        "str(BATCH_TYPE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pb6qPUvyh5A1"
      },
      "source": [
        "왜 일반적인 Python 유형을 정의할 수 없는지 궁금할 것입니다. [1부](custom_federated_algorithms_1.ipynb)에서 논의한 내용을 상기해 보세요. 즉, Python을 사용하여 TFF 계산의 논리를 표현할 수 있지만 내부적으로 TFF 계산은 Python이 *아니라고* 했습니다. 위에 정의한 `BATCH_TYPE` 기호는 추상적인 TFF 유형 사양을 나타냅니다. 이 *추상적* TFF 유형을 구체적인 Python *표현* 유형(예: Python 함수 본문에서 TFF 유형을 나타내는 데 사용할 수 있는 `dict` 또는 `collections.namedtuple`과 같은 컨테이너)과 구분하는 것이 중요합니다. Python과 달리 TFF에는 개별적으로 명명하거나 명명하지 않을 수 있는 요소와 함께 튜플과 유사한 컨테이너에 대한 단일 추상 유형 생성자 `tff.StructType`가 있습니다. TFF 계산은 공식적으로 하나의 매개변수와 하나의 결과만 선언할 수 있으므로 이 유형은 계산의 정식 매개변수를 모델링하는 데도 사용됩니다. 이에 대한 예제를 곧 살펴볼 것입니다.\n",
        "\n",
        "이제 TFF 유형의 모델 매개변수를 이번에도 TFF 명명 튜플인 *weights* 및 *bias*로 정의해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Og7VViafh-30"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "<weights=float32[784,10],bias=float32[10]>\n"
          ]
        }
      ],
      "source": [
        "MODEL_SPEC = collections.OrderedDict(\n",
        "    weights=tf.TensorSpec(shape=[784, 10], dtype=tf.float32),\n",
        "    bias=tf.TensorSpec(shape=[10], dtype=tf.float32))\n",
        "MODEL_TYPE = tff.to_type(MODEL_SPEC)\n",
        "\n",
        "print(MODEL_TYPE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iHhdaWSpfQxo"
      },
      "source": [
        "이러한 정의가 적용되면 이제 단일 배치에서 주어진 모델에 대한 손실을 정의할 수 있습니다. `@tf.function` 데코레이터 내부의 `@tff.tf_computation` 데코레이터 사용에 유의하세요. 이를 통해 `tff.tf_computation` 데코레이터에 의해 생성된 `tf.Graph` 컨텍스트 내부에 있음에도 불구하고 의미 체계와 같은 Python을 사용하여 TF를 작성할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4EObiz_Ke0uK"
      },
      "outputs": [],
      "source": [
        "# NOTE: `forward_pass` is defined separately from `batch_loss` so that it can \n",
        "# be later called from within another tf.function. Necessary because a\n",
        "# @tf.function  decorated method cannot invoke a @tff.tf_computation.\n",
        "\n",
        "@tf.function\n",
        "def forward_pass(model, batch):\n",
        "  predicted_y = tf.nn.softmax(\n",
        "      tf.matmul(batch['x'], model['weights']) + model['bias'])\n",
        "  return -tf.reduce_mean(\n",
        "      tf.reduce_sum(\n",
        "          tf.one_hot(batch['y'], 10) * tf.math.log(predicted_y), axis=[1]))\n",
        "\n",
        "@tff.tf_computation(MODEL_TYPE, BATCH_TYPE)\n",
        "def batch_loss(model, batch):\n",
        "  return forward_pass(model, batch)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8K0UZHGnr8SB"
      },
      "source": [
        "예상하는 바와 같이 계산 `batch_loss`는 주어진 모델과 단일 데이터를 고려하여 `float32` 손실을 반환합니다. `MODEL_TYPE` 및 `BATCH_TYPE`이 2-튜플 공식 매개변수로 묶여진 것에 주목하세요. `batch_loss` 유형을 `(<MODEL_TYPE,BATCH_TYPE> -> float32)`로 인식할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4WXEAY8Nr89V"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>> -> float32)'"
            ]
          },
          "execution_count": 13,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "str(batch_loss.type_signature)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pAnt_UcdnvGa"
      },
      "source": [
        "온전성 검사로 0으로 채워진 초기 모델을 구성하고 위에서 시각화한 데이터 배치에 대해 손실을 계산해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "U8Ne8igan3os"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "2.3025854"
            ]
          },
          "execution_count": 14,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "initial_model = collections.OrderedDict(\n",
        "    weights=np.zeros([784, 10], dtype=np.float32),\n",
        "    bias=np.zeros([10], dtype=np.float32))\n",
        "\n",
        "sample_batch = federated_train_data[5][-1]\n",
        "\n",
        "batch_loss(initial_model, sample_batch)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ckigEAyDAWFV"
      },
      "source": [
        "초기 모델을 정의하는 Python 함수 본문이 모델 매개변수를 `model['weight']` 및 `model['bias']`로 소비하지만 이 모델을 `dict`으로 정의하여 TFF 계산을 제공한다는 점에 유의하세요. `batch_loss`에 대한 호출의 인수는 단순히 이 함수의 본문에 전달되는 것이 아닙니다.\n",
        "\n",
        "`batch_loss`를 호출하면 어떻게 될까요? `batch_loss`의 Python 본문은 이미 정의된 위 셀에서 추적 및 직렬화되었습니다. TFF는 계산 정의 시간에 `batch_loss`에 대한 호출자 역할을 하고 `batch_loss`가 호출될 때 호출 대상 역할을 합니다. 두 역할 모두에서 TFF는 TFF의 추상 유형 시스템과 Python 표현 유형 사이에서 가교 역할을 합니다. 호출 시 TFF는 대부분의 표준 Python 컨테이너 유형(`dict`, `list`, `tuple`, `collections.namedtuple` 등)을 추상 TFF 튜플의 구체적인 표현으로 받아들입니다. 또한 위에서 언급했듯이 TFF 계산은 공식적으로 단일 매개변수만 허용하지만 매개변수 유형이 튜플인 경우 위치 및/또는 키워드 인수와 함께 익숙한 Python 호출 구문을 사용할 수 있습니다. 예상대로 작동합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eB510nILYbId"
      },
      "source": [
        "### 단일 배치에서의 경사 하강\n",
        "\n",
        "이제 이 손실 함수를 사용하여 경사 하강의 단일 스텝을 수행하는 계산을 정의해 보겠습니다. 이 함수를 정의할 때 `batch_loss`를 하위 구성 요소로 사용하는 방식에 유의하세요. 다른 계산의 본문 내에서 `tff.tf_computation`으로 구성된 계산을 호출할 수 있지만 일반적으로 필요하지 않습니다. 위에서 언급했듯이 직렬화로 일부 디버깅 정보가 손실되기 때문에 이러한 호출은 `tff.tf_computation` 데코레이터 없이 모든 TensorFlow를 작성하고 테스트하는 더욱 복잡한 계산에 바람직한 경우가 많습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "O4uaVxw3AyYS"
      },
      "outputs": [],
      "source": [
        "@tff.tf_computation(MODEL_TYPE, BATCH_TYPE, tf.float32)\n",
        "def batch_train(initial_model, batch, learning_rate):\n",
        "  # Define a group of model variables and set them to `initial_model`. Must\n",
        "  # be defined outside the @tf.function.\n",
        "  model_vars = collections.OrderedDict([\n",
        "      (name, tf.Variable(name=name, initial_value=value))\n",
        "      for name, value in initial_model.items()\n",
        "  ])\n",
        "  optimizer = tf.keras.optimizers.SGD(learning_rate)\n",
        "\n",
        "  @tf.function\n",
        "  def _train_on_batch(model_vars, batch):\n",
        "    # Perform one step of gradient descent using loss from `batch_loss`.\n",
        "    with tf.GradientTape() as tape:\n",
        "      loss = forward_pass(model_vars, batch)\n",
        "    grads = tape.gradient(loss, model_vars)\n",
        "    optimizer.apply_gradients(\n",
        "        zip(tf.nest.flatten(grads), tf.nest.flatten(model_vars)))\n",
        "    return model_vars\n",
        "\n",
        "  return _train_on_batch(model_vars, batch)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Y84gQsaohC38"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>,float32> -> <weights=float32[784,10],bias=float32[10]>)'"
            ]
          },
          "execution_count": 16,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "str(batch_train.type_signature)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ID8xg9FCUL2A"
      },
      "source": [
        "이러한 다른 함수의 본문 내에서 `tff.tf_computation`으로 데코레이팅된 Python 함수를 호출하면 내부 TFF 계산의 논리가 외부 논리에 포함됩니다(본질적으로 인라인이 됨). 위에서 언급했듯이 두 계산을 모두 작성하는 경우 내부 함수(이 경우 `batch_loss`)를 `tff.tf_computation` 대신 일반 Python 또는 `tf.function`으로 만드는 것이 좋습니다. 그러나 여기서는 다른 함수 내에서 어떤 함수의 `tff.tf_computation`을 호출하면 기본적으로 예상대로 동작함을 보여줍니다. 예를 들어 `batch_loss`를 정의하는 Python 코드가 없고 직렬화된 TFF 표현만 있는 경우에 이렇게 해야 할 수 있습니다.\n",
        "\n",
        "이제 이 함수를 초기 모델에 몇 번 적용하여 손실이 감소하는지 확인하겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8edcJTlXUULm"
      },
      "outputs": [],
      "source": [
        "model = initial_model\n",
        "losses = []\n",
        "for _ in range(5):\n",
        "  model = batch_train(model, sample_batch, 0.1)\n",
        "  losses.append(batch_loss(model, sample_batch))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3n1onojT1zHv"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "[0.19690022, 0.13176313, 0.10113226, 0.082738124, 0.0703014]"
            ]
          },
          "execution_count": 18,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "losses"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EQk4Ha8PU-3P"
      },
      "source": [
        "### 일련의 로컬 데이터에 대한 경사 하강\n",
        "\n",
        "이제 `batch_train`이 작동하는 것으로 보이므로 단일 배치가 아닌 한 사용자로부터 모든 배치의 전체 시퀀스를 소비하는 유사한 훈련 함수 `local_train`을 작성해 보겠습니다. 새 계산은 이제 `BATCH_TYPE` 대신 `tff.SequenceType(BATCH_TYPE)`을 사용해야 합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EfPD5a6QVNXM"
      },
      "outputs": [],
      "source": [
        "LOCAL_DATA_TYPE = tff.SequenceType(BATCH_TYPE)\n",
        "\n",
        "@tff.federated_computation(MODEL_TYPE, tf.float32, LOCAL_DATA_TYPE)\n",
        "def local_train(initial_model, learning_rate, all_batches):\n",
        "\n",
        "  # Mapping function to apply to each batch.\n",
        "  @tff.federated_computation(MODEL_TYPE, BATCH_TYPE)\n",
        "  def batch_fn(model, batch):\n",
        "    return batch_train(model, batch, learning_rate)\n",
        "\n",
        "  return tff.sequence_reduce(all_batches, initial_model, batch_fn)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "sAhkS5yKUgjC"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'(<<weights=float32[784,10],bias=float32[10]>,float32,<x=float32[?,784],y=int32[?]>*> -> <weights=float32[784,10],bias=float32[10]>)'"
            ]
          },
          "execution_count": 20,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "str(local_train.type_signature)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EYT-SiopYBtH"
      },
      "source": [
        "이 짧은 코드 섹션에는 몇 가지 세부 사항이 숨겨져 있으므로 하나씩 살펴보겠습니다.\n",
        "\n",
        "첫째, `tf.data.Dataset.reduce`를 사용하여 이전에 수행한 것과 유사하게 시퀀스를 처리하여 이 논리를 TensorFlow에서 완전히 구현할 수 있지만 이번에는 논리를 접착 언어에서 `tff.federated_computation`로 표현하도록 선택했습니다. 축소를 수행하기 위해 페더레이션 연산자 `tff.sequence_reduce`를 사용했습니다.\n",
        "\n",
        "연산자 `tff.sequence_reduce`는 `tf.data.Dataset.reduce`와 유사하게 사용됩니다. 기본적으로 `tf.data.Dataset.reduce`와 동일하다고 생각할 수 있지만, 기억하신다면, TensorFlow 코드를 포함할 수 없는 페더레이션 계산 내에서 사용하기 위한 것입니다. 이 연산자는 `T` 유형 요소의 *시퀀스*, 일부 유형 `U`의 초기 축소 상태(이를 추상적으로 *0*이라고 하겠음), 및 단일 요소를 처리하여 축소 상태를 변경하는 `(<U,T> -> U)` 유형의 *축소 연산자*로 구성된 정식 매개변수 3-튜플을 포함한 템플릿 연산자입니다. 결과는 모든 요소를 순차적으로 처리한 후 축소의 최종 상태입니다. 이 예에서 축소 상태는 데이터의 앞부분에 대해 훈련된 모델이고 요소는 데이터 배치입니다.\n",
        "\n",
        "둘째, 하나의 계산(`batch_train`)을 다른 계산( `local_train`) 내의 구성 요소로 다시 사용했지만 직접 사용하지는 않았다는 점에 주목하세요. 학습률이라는 추가 매개변수가 필요하기 때문에 이 계산을 축소 연산자로 사용할 수 없습니다. 이 문제를 해결하기 위해 본문에서 `local_train`의 매개변수 `learning_rate`에 바인딩되는 내장된 페더레이션 계산 `batch_fn`을 정의합니다. 하위 계산이 상위의 본문 내에서 호출되지만 않으면 이런 식으로 정의된 하위 계산이 상위 계산의 정식 매개변수를 포착할 수 있습니다. 이 패턴은 Python의 `functools.partial`과 동일한 것으로 생각할 수 있습니다.\n",
        "\n",
        "이러한 방식으로 `learning_rate`를 포착한다는 것의 실제 의미는 물론 모든 배치에서 동일한 학습률 값이 사용된다는 것입니다.\n",
        "\n",
        "이제 샘플 배치(숫자 `5`)를 제공한 동일한 사용자의 전체 데이터 시퀀스에 대해 새로 정의된 로컬 훈련 함수를 시도해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EnWFLoZGcSby"
      },
      "outputs": [],
      "source": [
        "locally_trained_model = local_train(initial_model, 0.1, federated_train_data[5])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "y0UXUqGk9zoF"
      },
      "source": [
        "효과가 있나요? 이 질문에 답하려면 평가를 구현해야 합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a8WDKu6WYy__"
      },
      "source": [
        "### 로컬 평가\n",
        "\n",
        "다음은 모든 데이터 배치에서 손실을 합산하여 로컬 평가를 구현하는 한 가지 방법입니다(방금 전에 평균도 계산할 수 있었을 테지만 독자를 위한 연습으로 남겨 두었음)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0RiODuc6z7Ln"
      },
      "outputs": [],
      "source": [
        "@tff.federated_computation(MODEL_TYPE, LOCAL_DATA_TYPE)\n",
        "def local_eval(model, all_batches):\n",
        "  # TODO(b/120157713): Replace with `tff.sequence_average()` once implemented.\n",
        "  return tff.sequence_sum(\n",
        "      tff.sequence_map(\n",
        "          tff.federated_computation(lambda b: batch_loss(model, b), BATCH_TYPE),\n",
        "          all_batches))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pH2XPEAKa4Dg"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>*> -> float32)'"
            ]
          },
          "execution_count": 23,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "str(local_eval.type_signature)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "efX81HuE-BcO"
      },
      "source": [
        "다시 말하지만, 이 코드에 설명된 몇 가지 새로운 요소가 있습니다. 하나씩 살펴보겠습니다.\n",
        "\n",
        "첫째, 시퀀스를 처리하기 위한 두 개의 새로운 페더레이션 연산자를 사용했습니다. 이 두 가지 연산자는 각각, *매핑 함수* `T->U`와 `T`의 *시퀀스*를 받아서 매핑 함수를 포인트 단위로 적용하여 얻은 `U`의 시퀀스를 출력하는 `tff.sequence_map`, 그리고 모든 요소를 합산하는 `tff.sequence_sum`입니다. 여기에서는 각 데이터 배치를 손실 값에 매핑한 다음 결과적인 손실 값을 추가하여 총 손실을 계산합니다.\n",
        "\n",
        "이번에도 `tff.sequence_reduce`를 사용할 수 있었지만 이것이 최선의 선택은 아닙니다. 축소 프로세스는 정의에 따라 순차적인 반면 매핑과 합계는 병렬로 계산될 수 있습니다. 선택이 주어지면 구현 선택을 제한하지 않는 연산자를 고수하는 것이 가장 좋습니다. 그러면 향후 특정 환경에 배포하기 위해 TFF 계산을 컴파일할 때 모든 잠재적 기회를 최대한 활용하여 더 빠르고 확장 가능하며 자원 효율적인 실행 결과를 얻을 수 있기 때문입니다.\n",
        "\n",
        "둘째, `local_train`에서와 마찬가지로 필요한 구성 요소 함수(`batch_loss`)는 페더레이션 연산자(`tff.sequence_map`)가 예상하는 것보다 많은 매개변수를 사용한다는 점에 주목하세요. 그래서 마찬가지로 `lambda`를 `tff.federated_computation`으로 직접 래핑하여 이번에는 인라인으로 일부를 정의합니다. 인수로서 래퍼를 함수와 인라인으로 사용하는 방법은 `tff.tf_computation`를 사용하여 TensorFlow 논리를 TFF에 포함하는 권장 방식입니다.\n",
        "\n",
        "이제 훈련이 효과가 있었는지 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vPw6JSVf5q_x"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "initial_model loss = 23.025854\n",
            "locally_trained_model loss = 0.4348469\n"
          ]
        }
      ],
      "source": [
        "print('initial_model loss =', local_eval(initial_model,\n",
        "                                         federated_train_data[5]))\n",
        "print('locally_trained_model loss =',\n",
        "      local_eval(locally_trained_model, federated_train_data[5]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6Tvu70cnBsUf"
      },
      "source": [
        "실제로 손실이 감소했습니다. 하지만 다른 사용자의 데이터에서 평가했을 때는 어떻게 될까요?"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gjF0NYAj5wls"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "initial_model loss = 23.025854\n",
            "locally_trained_model loss = 74.50075\n"
          ]
        }
      ],
      "source": [
        "print('initial_model loss =', local_eval(initial_model,\n",
        "                                         federated_train_data[0]))\n",
        "print('locally_trained_model loss =',\n",
        "      local_eval(locally_trained_model, federated_train_data[0]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7WPumnRTBzUs"
      },
      "source": [
        "예상대로 상황이 악화되었습니다. 이 모델은 `5`를 인식하도록 훈련되었으며 `0`을 본 적이 없습니다. 그러면 이제 로컬 훈련이 글로벌 관점에서 모델의 품질에 어떤 영향을 미쳤을까?라는 의문이 듭니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QJnL2mQRZKTO"
      },
      "source": [
        "### 페더레이션 평가\n",
        "\n",
        "마침내 긴 여정을 거쳐 처음 출발했던 주제인 페더레이션 유형과 페더레이션 계산으로 다시 돌아왔습니다. 다음은 서버에서 비롯되는 모델에 대한 한 쌍의 TFF 유형 정의와 클라이언트에 남아 있는 데이터입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LjGGhpoEBh_6"
      },
      "outputs": [],
      "source": [
        "SERVER_MODEL_TYPE = tff.FederatedType(MODEL_TYPE, tff.SERVER)\n",
        "CLIENT_DATA_TYPE = tff.FederatedType(LOCAL_DATA_TYPE, tff.CLIENTS)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4gTXV2-jZtE3"
      },
      "source": [
        "지금까지 소개한 모든 정의를 이용해 TFF에서 페더레이션 평가를 한 줄로 표현할 수 있습니다. 즉, 모델을 클라이언트에 배포하고 각 클라이언트가 데이터의 로컬 부분에 대해 로컬 평가를 호출한 다음 손실을 평균화하도록 한다는 것입니다. 다음은 이것을 작성하는 한 가지 방법입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2zChEPzEBx4T"
      },
      "outputs": [],
      "source": [
        "@tff.federated_computation(SERVER_MODEL_TYPE, CLIENT_DATA_TYPE)\n",
        "def federated_eval(model, data):\n",
        "  return tff.federated_mean(\n",
        "      tff.federated_map(local_eval, [tff.federated_broadcast(model), data]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IWcNONNWaE0N"
      },
      "source": [
        "더 간단한 시나리오에서 `tff.federated_mean` 및 `tff.federated_map`의 예를 이미 보았고, 직관적인 수준에서는 예상대로 작동하지만 이 코드 섹션에는 그 이상의 의미가 담겨 있습니다. 그러면 찬찬히 살펴보도록 하겠습니다.\n",
        "\n",
        "먼저, *각 클라이언트가 데이터의 로컬 부분에 대해 로컬 평가를 호출하도록 한다*는 부분을 자세히 분석하겠습니다. 이전 섹션의 내용을 기억하겠지만 `local_eval`에는 `(<MODEL_TYPE, LOCAL_DATA_TYPE> -> float32)` 형식의 형식 서명이 있습니다.\n",
        "\n",
        "페더레이션 연산자 `tff.federated_map`은 일부 유형 `T->U`의 *매핑 함수*와 `{T}@CLIENTS` 유형의 페더레이션 값으로 구성된 2-튜플을 매개변수로 받아들이고(즉, 매핑 함수의 매개변수와 동일한 유형의 구성원 구성 요소) `{U}@CLIENTS` 유형의 결과를 반환하는 템플릿입니다.\n",
        "\n",
        "클라이언트별로 적용할 매핑 함수로 `local_eval`을 제공하므로 두 번째 인수는 페더레이션 유형 `{<MODEL_TYPE, LOCAL_DATA_TYPE>}@CLIENTS`이어야 합니다. 즉, 이전 섹션의 명명법에 따라 페더레이션 튜플이어야 합니다. 각 클라이언트는 `local_eval`에 대한 전체 인수 집합을 구성원 구성 요소로 가지고 있어야 합니다. 대신, 여기서는 2-요소 Python `list`를 제공합니다. 어떤 일이 일어날까요?\n",
        "\n",
        "`float`를 받아들이는 함수에 `int`를 제공할 때와 같이 다른 곳에서 경험했을 수 있는 암시적 형식 캐스팅과 비슷하게, 이 예는 실제로 TFF에서 *암시적 형식 캐스팅*을 보여줍니다. 암시적 캐스팅은 이 시점에서 거의 사용되지 않지만 상용구를 최소화하기 위한 방법으로 TFF에 더 많이 도입할 계획입니다.\n",
        "\n",
        "이 경우에 적용되는 암시적 캐스팅은 `{<X,Y>}@Z` 형식의 페더레이션 튜플과 페더레이션 값 `<{X}@Z,{Y}@Z>` 튜플 간의 동등성입니다. 공식적으로 이 두 가지 유형은 프로그래머의 관점에서 보면 서로 다른 형식 서명이지만 `Z`의 각 기기는 두 단위의 데이터 `X` 및 `Y`를 보유합니다. 여기서 일어나는 상황은 Python의 `zip`과 다르지 않으며 실제로 이러한 변환을 명시적으로 수행할 수 있도록 해주는 연산자 `tff.federated_zip`을 제공합니다. `tff.federated_map`에 두 번째 인수로 튜플이 제공되면 단순히 `tff.federated_zip`을 자동으로 호출합니다.\n",
        "\n",
        "위와 같이 이제 `tff.federated_broadcast(model)` 표현식을 TFF 유형 `{MODEL_TYPE}@CLIENTS`의 값을 나타내는 것으로, 그리고 `data`를 TFF 유형 `{LOCAL_DATA_TYPE}@CLIENTS`(또는 간단히 `CLIENT_DATA_TYPE`)의 값을 나타내는 것으로 생각할 수 있습니다. 이 두 가지 유형은 암시적 `tff.federated_zip`를 통해 함께 필터링되어 `tff.federated_map`에 대한 두 번째 인수를 형성합니다.\n",
        "\n",
        "연산자 `tff.federated_broadcast`는 예상대로 서버에서 클라이언트로 단순히 데이터를 전송합니다.\n",
        "\n",
        "이제 로컬 훈련이 시스템의 평균 손실에 어떤 영향을 미쳤는지 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tbmtJItcn94j"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "initial_model loss = 23.025852\n",
            "locally_trained_model loss = 54.432625\n"
          ]
        }
      ],
      "source": [
        "print('initial_model loss =', federated_eval(initial_model,\n",
        "                                             federated_train_data))\n",
        "print('locally_trained_model loss =',\n",
        "      federated_eval(locally_trained_model, federated_train_data))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LQi2rGX_fK7i"
      },
      "source": [
        "실제로 예상하는 바와 같이 손실이 증가했습니다. 모든 사용자를 위해 모델을 개선하려면 각 사용자의 데이터에 대해 훈련이 필요합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vkw9f59qfS7o"
      },
      "source": [
        "### 페더레이션 훈련\n",
        "\n",
        "페더레이션 훈련을 구현하는 가장 간단한 방법은 로컬에서 훈련한 다음 모델을 평균화하는 것입니다. 그러면 아래에서 볼 수 있듯이 이미 논의한 동일한 빌딩 블록과 패턴을 사용합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mBOC4uoG6dd-"
      },
      "outputs": [],
      "source": [
        "SERVER_FLOAT_TYPE = tff.FederatedType(tf.float32, tff.SERVER)\n",
        "\n",
        "\n",
        "@tff.federated_computation(SERVER_MODEL_TYPE, SERVER_FLOAT_TYPE,\n",
        "                           CLIENT_DATA_TYPE)\n",
        "def federated_train(model, learning_rate, data):\n",
        "  return tff.federated_mean(\n",
        "      tff.federated_map(local_train, [\n",
        "          tff.federated_broadcast(model),\n",
        "          tff.federated_broadcast(learning_rate), data\n",
        "      ]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "z2vACMsQjzO1"
      },
      "source": [
        "모델을 평균화하는 대신 `tff.learning`에서 제공하는 페더레이션 평균화를 모든 기능을 갖춰 구현할 때는 업데이트 기준을 자를 수 있고 압축에 유리한 점 등 여러 가지 이유로 모델 차이를 평균화하는 것이 바람직합니다.\n",
        "\n",
        "몇 차례의 훈련을 실행하고 전후의 평균 손실을 비교하여 훈련이 제대로 작동하는지 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NLx-3rLs9jGY"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "round 0, loss=21.60552406311035\n",
            "round 1, loss=20.365678787231445\n",
            "round 2, loss=19.27480125427246\n",
            "round 3, loss=18.31110954284668\n",
            "round 4, loss=17.45725440979004\n"
          ]
        }
      ],
      "source": [
        "model = initial_model\n",
        "learning_rate = 0.1\n",
        "for round_num in range(5):\n",
        "  model = federated_train(model, learning_rate, federated_train_data)\n",
        "  learning_rate = learning_rate * 0.9\n",
        "  loss = federated_eval(model, federated_train_data)\n",
        "  print('round {}, loss={}'.format(round_num, loss))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Z0VjSLQzlUIp"
      },
      "source": [
        "완전하게 하기 위해 이제 테스트 데이터에서 실행하여 모델이 잘 일반화되는지 확인하겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZaZT45yFMOaM"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "initial_model test loss = 22.795593\n",
            "trained_model test loss = 17.278767\n"
          ]
        }
      ],
      "source": [
        "print('initial_model test loss =',\n",
        "      federated_eval(initial_model, federated_test_data))\n",
        "print('trained_model test loss =', federated_eval(model, federated_test_data))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pxlHHwLGlgFB"
      },
      "source": [
        "이것으로 튜토리얼을 마칩니다.\n",
        "\n",
        "물론 여기서 단순화시킨 예는 보다 현실적인 시나리오에서 수행해야 하는 많은 작업을 반영하지 않습니다. 예를 들어 손실 이외의 메트릭을 계산하지 않았습니다. 보다 완전한 예로 `tff.learning`에서 페더레이션 평균화의 [구현](https://github.com/tensorflow/federated/blob/master/tensorflow_federated/python/learning/federated_averaging.py)을 연구해보는 것도 좋을 것입니다. 권장되는 코딩 방식을 실제로 확인하는 기회도 될 것입니다."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "custom_federated_algorithms_2.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
